package deployr.cli;

import java.io.*;
import java.util.*;

import org.mortbay.component.*;
import org.mortbay.jetty.*;
import org.mortbay.jetty.deployer.*;
import org.mortbay.jetty.handler.*;
import org.mortbay.jetty.nio.*;
import org.mortbay.jetty.security.*;
import org.mortbay.jetty.servlet.*;
import org.mortbay.thread.*;

import deployr.core.*;
import deployr.core.FileSystem;

public class JettyServer implements DeployListener {

	public static final void overrideLog() {
		System.setProperty("org.mortbay.log.class", "deployr.cli.JettyLog");
	}

	private Server server;

	private Thread stop = new Thread(new Runnable() {
		@Override
		public void run() {
			stop();
		}
	});

	private Map<String, Handler> handlersMap;

	private FileSystem jettyHome;

	private ContextDeployer contextDeployer;

	private JettyDriver driver;

	private Connector connector(String host, int port) {
		SelectChannelConnector connector = new SelectChannelConnector();
		connector.setHost(host);
		connector.setPort(port);
		connector.setMaxIdleTime(30000);
		connector.setAcceptors(2);
		connector.setStatsOn(false);
		connector.setConfidentialPort(8443);
		connector.setLowResourcesConnections(5000);
		connector.setLowResourceMaxIdleTime(5000);
		return connector;
	}

	private LifeCycle contextDeployer() {
		try {
			contextDeployer = new ContextDeployer();
			contextDeployer.setContexts(handlerContexts());
			contextDeployer.setConfigurationDir(jettyHome.absolutePath("contexts"));
			contextDeployer.setScanInterval(1);
			return contextDeployer;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private ContextHandlerCollection handlerContexts() {
		return (ContextHandlerCollection) handlersMap.get("Contexts");
	}

	private RequestLogHandler handlerRequestLog() {
		return (RequestLogHandler) handlersMap.get("RequestLog");
	}

	private Handler handlers() {
		handlersMap();

		HandlerCollection handlers1 = new HandlerCollection();
		handlers1.addHandler(handlerContexts());
		handlers1.addHandler(handlerRequestLog());

		HandlerCollection handlers2 = new HandlerCollection();
		handlers2.addHandler(handlerDefault());
		handlers2.addHandler(handlerRequestLog());
		
		HandlerList handlers = new HandlerList();
		handlers.addHandler(handlers1);
		handlers.addHandler(handlers2);
		
		return handlers;
	}

	private Handler handlerDefault() {
		return handlersMap.get("Default");
	}

	private void handlersMap() {
		handlersMap = new HashMap<String, Handler>();
		handlersMap.put("Default", new DeployrHandler(this));
		handlersMap.put("Contexts", new ContextHandlerCollection());
		// handlersMap.put("Default", new DefaultHandler());
		handlersMap.put("RequestLog", new RequestLogHandler());
	}

	private void hook(Thread stop) {
		Runtime.getRuntime().addShutdownHook(stop);
	}

	private void initRequestLog() {
		NCSARequestLog requestLog = new NCSARequestLog();
		requestLog.setFilename(jettyHome.absolutePath("logs/yyyy_mm_dd.request.log"));
		requestLog.setFilenameDateFormat("yyyy_MM_dd");
		requestLog.setRetainDays(90);
		requestLog.setAppend(true);
		requestLog.setExtended(false);
		requestLog.setLogTimeZone("GMT");

		handlerRequestLog().setRequestLog(requestLog);
	}

	public void loop() {
		hook(stop);
		while (true) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
		}
	}

	private void newServer(String host, int port, File jettyDir, String webappsDir) {

		server = new Server();
		
		server.setThreadPool(threadPool());
		server.addConnector(connector(host, port));
		server.setHandler(handlers());
		server.addLifeCycle(contextDeployer());
		server.addLifeCycle(webappDeployer(webappsDir));
		server.setUserRealms(userRealms());

		initRequestLog();

		server.setStopAtShutdown(true);
		server.setSendServerVersion(true);
		server.setSendDateHeader(true);
		server.setGracefulShutdown(1000);
	}

	private void overrideJettyHome(File jettyDir) {
		System.setProperty("jetty.home", jettyDir.getAbsolutePath());
	}

	private void prepare(File jettyDir) {
		jettyHome = new FileSystem(jettyDir);
		jettyHome.mkdirs("contexts");
		jettyHome.mkdirs("webapps");
		jettyHome.mkdirs("etc");
		jettyHome.mkdirs("logs");
		jettyHome.put("etc/realm.properties", "".getBytes());
	}

	public void run(String host, int port, File jettyDir, File webappsDir) {
		overrideLog();
		overrideJettyHome(jettyDir);
		prepare(jettyDir);
		newServer(host, port, jettyDir, webappsDir.getAbsolutePath());
		newDriver(webappsDir);
		tryStart();
	}

	private void newDriver(File webappsDir) {
		FileSystem origin = new FileSystem(webappsDir);
		FileSystem target = new FileSystem(jettyHome.get("webapps"));
		Wars wars = new Wars(origin, target);
		JettyContexts contexts = new JettyContexts(new FileSystem(jettyHome.get("contexts")));
		driver = new JettyDriver(wars, contexts);
	}

	public void stop() {
		tryStop();
	}

	private UserRealm testRealm() throws Exception {
		HashUserRealm userRealm = new HashUserRealm();
		userRealm.setName("Test Realm");
		userRealm.setConfig(jettyHome.absolutePath("etc/realm.properties"));
		userRealm.setRefreshInterval(1000);
		return userRealm;
	}

	private ThreadPool threadPool() {
		QueuedThreadPool threadPool = new QueuedThreadPool();
		threadPool.setMinThreads(10);
		threadPool.setMaxThreads(200);
		threadPool.setLowThreads(20);
		threadPool.setSpawnOrShrinkAt(2);
		return threadPool;
	}

	private void tryStart() {
		try {
			if (null != server) server.start();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void tryStop() {
		try {
			server.stop();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private UserRealm[] userRealms() {
		try {
			UserRealm[] userRealms = new UserRealm[1];
			userRealms[0] = testRealm();
			return userRealms;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private LifeCycle webappDeployer(String webappsDir) {
		WebAppDeployer webAppDeployer = new WebAppDeployer();
		webAppDeployer.setContexts(handlerContexts());
		webAppDeployer.setWebAppDir(webappsDir);
		webAppDeployer.setParentLoaderPriority(false);
		webAppDeployer.setExtract(true);
		webAppDeployer.setAllowDuplicates(false);
		return webAppDeployer;
	}

	@Override
	public void willDeploy(String appId) {
		driver.deploy(appId);
	}

	public File getJettyHome() {
		return jettyHome.getFile();
	}
}
